package com.fiberhome.opticalbox.view;

import com.fiberhome.opticalbox.utils.LogUtil;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.Scroller;

/**
 * @author wangpeng
 */
public class PictureView extends FrameLayout {
	private Scroller mScroller;

	private boolean is2doAnim = false;
	private boolean is2Scale = false;
	private int mDuration = 600;

	private Matrix matrix = new Matrix();
	private Bitmap sourceBitmap;

	private double downDistance;

	private float scaleSize = 1;
	private float tmpScaleSize = scaleSize;

	private int mWidth, mHeight;

	private static final float MAX_SCALE_SIZE = 6;
	private static final float MIN_SCALE_SIZE = 0.8f;
	private static final float LIMIT_MAX_SCALE_SIZE = MAX_SCALE_SIZE * 1.5f;
	private static final float LIMIT_MIN_SCALE_SIZE = MIN_SCALE_SIZE * 0.5f;

	private Handler handler = new Handler();

	public PictureView(Context context) {
		this(context, null);
	}

	public PictureView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public PictureView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		setBackgroundColor(0xff00000);
		mScroller = new Scroller(context, new DecelerateInterpolator());
	}

	public void setImageBitmap(Bitmap bitmap) {
		if (bitmap == null) {
			LogUtil.e("PictureView setImageBitmap() set null bitmap");
			return;
		}
		sourceBitmap = bitmap;
		post(new Runnable() {
			@Override public void run() {
				scrollTo(-calcCenterX(), -calcCenterY());
			}
		});
	}

	@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		if (changed) {
			mWidth = right - left;
			mHeight = bottom - top;
		}
	}

	@Override public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			is2doAnim = false;
			return true;
		case MotionEvent.ACTION_MOVE:
			if (event.getPointerCount() > 1) {
				is2Scale = true;
				if (downDistance == 0) {
					downDistance = calcDistanceBetweenFingers(event);
				}

				scaleContent(event);
			} else {
				if (is2Scale) {
					return true;
				}
			}
			break;
		case MotionEvent.ACTION_POINTER_UP:
			tmpScaleSize = scaleSize;
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			is2doAnim = true;
			is2Scale = false;
			downDistance = 0;
			tmpScaleSize = scaleSize;
			dealTouchMoveResult();
			break;
		}
		return super.onTouchEvent(event);
	}

	private void dealTouchMoveResult() {
		if (isWidthOverflow() && isHeightOverflow()) {
			// both overflow
			int overflowX = 0;
			if ((sourceBitmap.getWidth() * (scaleSize - 1)) / 2f < -getScrollX()) {
				// left edge
				overflowX = -(int) ((sourceBitmap.getWidth() * scaleSize - mWidth) / 2);
			} else if ((sourceBitmap.getWidth() * (scaleSize - 1)) / 2f + (mWidth - sourceBitmap.getWidth() * scaleSize) > -getScrollX()) {
				// right edge
				overflowX = (int) ((sourceBitmap.getWidth() * scaleSize - mWidth) / 2);
			}
			int overflowY = 0;
			if ((sourceBitmap.getHeight() * (scaleSize - 1)) / 2 < -getScrollY()) {
				// top edge
				overflowY = -(int) ((sourceBitmap.getHeight() * scaleSize - mHeight) / 2);
			} else if ((sourceBitmap.getHeight() * (scaleSize - 1)) / 2f + (mHeight - sourceBitmap.getHeight() * scaleSize) > -getScrollY()) {
				// bottom edge
				overflowY = (int) ((sourceBitmap.getHeight() * scaleSize - mHeight) / 2);
			}

			startMoveAnim(getScrollX(), getScrollY(), overflowX != 0 ? (-getScrollX() - calcCenterX() + overflowX) : 0,
					overflowY != 0 ? (-getScrollY() - calcCenterY() + overflowY) : 0, mDuration);
		} else if (isWidthOverflow()) {
			// just width overflow
			if ((sourceBitmap.getWidth() * (scaleSize - 1)) / 2f < -getScrollX()) {
				// left edge
				int overflowX = (int) ((sourceBitmap.getWidth() * scaleSize - mWidth) / 2);
				startMoveAnim(getScrollX(), getScrollY(), -getScrollX() - calcCenterX() - overflowX, -getScrollY() - calcCenterY(),
						mDuration);
			} else if ((sourceBitmap.getWidth() * (scaleSize - 1)) / 2f + (mWidth - sourceBitmap.getWidth() * scaleSize) > -getScrollX()) {
				// right edge
				int overflowX = (int) ((sourceBitmap.getWidth() * scaleSize - mWidth) / 2);
				startMoveAnim(getScrollX(), getScrollY(), -getScrollX() - calcCenterX() + overflowX, -getScrollY() - calcCenterY(),
						mDuration);
			} else {
				startMoveAnim(getScrollX(), getScrollY(), 0, -getScrollY() - calcCenterY(), mDuration);
			}
		} else if (isHeightOverflow()) {
			// just height overflow
			if ((sourceBitmap.getHeight() * (scaleSize - 1)) / 2 < -getScrollY()) {
				// top edge
				int overflowY = (int) ((sourceBitmap.getHeight() * scaleSize - mHeight) / 2);
				startMoveAnim(getScrollX(), getScrollY(), -getScrollX() - calcCenterX(), -getScrollY() - calcCenterY() - overflowY,
						mDuration);
			} else if ((sourceBitmap.getHeight() * (scaleSize - 1)) / 2f + (mHeight - sourceBitmap.getHeight() * scaleSize) > -getScrollY()) {
				// bottom edge
				int overflowY = (int) ((sourceBitmap.getHeight() * scaleSize - mHeight) / 2);
				startMoveAnim(getScrollX(), getScrollY(), -getScrollX() - calcCenterX(), -getScrollY() - calcCenterY() + overflowY,
						mDuration);
			} else {
				startMoveAnim(getScrollX(), getScrollY(), -getScrollX() - calcCenterX(), 0, mDuration);
			}
		} else {
			// back to center
			startMoveAnim(getScrollX(), getScrollY(), -getScrollX() - calcCenterX(), -getScrollY() - calcCenterY(), mDuration);
		}

		startScaleAnim();
	}

	private void startScaleAnim() {
		handler.removeCallbacks(run);
		handler.post(run);
	}

	private void startMoveAnim(int startX, int startY, int dx, int dy, int duration) {
		mScroller.startScroll(startX, startY, dx, dy, duration);
		invalidate();
	}

	private Runnable run = new Runnable() {
		@Override public void run() {
			if (scaleSize > MAX_SCALE_SIZE) {
				scaleSize = scaleSize - 0.4f;
				tmpScaleSize = scaleSize;
				invalidate();
				handler.postDelayed(run, 10);
			} else if (scaleSize < MIN_SCALE_SIZE) {
				scaleSize = scaleSize + 0.1f;
				tmpScaleSize = scaleSize;
				invalidate();
				handler.postDelayed(run, 10);
			}
		}
	};

	@Override public void computeScroll() {
		if (is2doAnim && mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
		super.computeScroll();
	}

	private double calcDistanceBetweenFingers(MotionEvent event) {
		float disX = Math.abs(event.getX(0) - event.getX(1));
		float disY = Math.abs(event.getY(0) - event.getY(1));
		return Math.sqrt(disX * disX + disY * disY);
	}

	private void scaleContent(MotionEvent event) {
		double newDistance = calcDistanceBetweenFingers(event);
		scaleSize = (float) (tmpScaleSize + (newDistance - downDistance) / (mWidth / 4));
		if (scaleSize > LIMIT_MAX_SCALE_SIZE) {
			scaleSize = LIMIT_MAX_SCALE_SIZE;
		} else if (scaleSize < LIMIT_MIN_SCALE_SIZE) {
			scaleSize = LIMIT_MIN_SCALE_SIZE;
		}
		invalidate();
	}

	@Override protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		if (sourceBitmap != null) {
			matrix.reset();
			matrix.postScale(scaleSize, scaleSize, sourceBitmap.getWidth() / 2, sourceBitmap.getHeight() / 2);
			matrix.postRotate(0, sourceBitmap.getWidth() / 2, sourceBitmap.getHeight() / 2);

			canvas.drawBitmap(sourceBitmap, matrix, null);
		}
	}

	private boolean isWidthOverflow() {
		return sourceBitmap.getWidth() * scaleSize > mWidth;
	}

	private boolean isHeightOverflow() {
		return sourceBitmap.getHeight() * scaleSize > mHeight;
	}

	private int calcCenterX() {
		return (int) (mWidth - sourceBitmap.getWidth()) / 2;
	}

	private int calcCenterY() {
		return (int) (mHeight - sourceBitmap.getHeight()) / 2;
	}
}
